// 虚函数和函数联编

#include "54PolymorphismCharacteristics.hpp"
// 虚函数的作用参考上面的54。
// 程序调用函数时将使用哪个可执行代码块呢？编译器负责回答这个问题!
// 将源代码中函数调用解释为执行特定函数代码块的过程被称为函数联编。

// c/c++编译器可以在编译过程中完成这种联编，在编译过程中的联编过程，被称为静态联编,又称为早起联编
// 虚函数的出现让这项工作变得困难,因为这个过程不是在编译期间能够确定的,就像Brass指针数组一样,你也不知道里面放的是Brass还是BrassPlus(他们都被声明为Brass指针)
// 所以编译器必须生成能够在程序运行时选择正确的虚方法的代码，这被称为动态联编，又称晚期联编。

// 在C++中，动态联编与 通过指针和引用调用方法相关，从某种程度上来说，这是由继承控制的。
// 在一般情况下，C++不允许将一种类型的地址赋给另一种类型的指针，也不允许一种类型的引用指向另一种类型。
// double x = 10;
// int *p_int = &x;        // invalid(不允许)
// 但是正如你看到的指向激烈的引用或指针，可以引用太生类对象，而不必进行显示类型转换
// 例如下面的初始化都是被允许的:
BrassPlus dilly("aquawius");
Brass *p_brass = &dilly; // allowed
Brass &r_brass = dilly;  // allowed

// !!!! 将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting),这使得公有继承不需要进行显示类型转换。 !!!!
// 所以可以对Brass对象进行的任何操作都适用于BrassPlus对象。

// !!!! 相反的过程--将基类指针或引用转化为派生类指针或引用称为向下强制转换(downcasting).但是如果要进行向下强制转换，那么必须使用显示类型转换。!!!!
// 如果允许隐式向下强制转换，则无意间可能将现有的对象创建了一个不存在的方法或者属性。

// 为了让程序能够在运行阶段进行决策，必须采取一些方法来跟踪积累指针或引用指向的对象类型，这增加了额外的处理开销。
// 如果派生类不重新定义基类的任何方法，当然也就不需要动态联编,由于使用静态联编的效率更高，因此被设置为C++默认的选择
// 因为C++的指导原则之一是不要为不使用的特性而付出代价,仅当程序设计确实需要虚函数时,才使用它们。
// 总之,编译器对虚方法使用动态联编


//   !!!   虚函数的工作原理  !!!
// 通常编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员。
// 这个隐藏成员里面保存了一个指向函数地址数组的指针, 这个数组我们称之为 虚函数表.
// 例如基类对象包含一个指针，这个指针指向基类中所有虚函数的地址表, 如果派生类提供了虚函数的新定义，则虚函数表中将保存新函数的地址.
// 那么通过指针进行调用的时候，首先将会访问这个虚函数的地址表, 访问对应地址的函数, 当然那个地址保存着将是基类或派生类的函数, 从而调用基类或派生类中重写的方法。
// 如果派生类中定义了新的虚函数，那么该函数的地址也会被添加到虚函数表中, 无论类中包含的虚函数是1个还是100个，都只需要在对象中添加一个地址成员,只是函数表的大小变了而已。

// 总结:
// 使用虚函数时，在内存和执行速度方面都有一定的成本
//  - 每个对象都将增大, 增大的量为储存地址的空间
//  - 对于每个类编译器都创建一个虚函数地址表(数组)
//  - 对于每个函数调用都需要执行一项额外的操作，即到虚函数地址表中查找函数地址。
// 虽然使用非虚函数的效率比虚函数要高，但是它不具备动态联编功能, 也就无法实现多态。



